# -*- coding: utf-8 -*-

"""
Created on Fri Jun  8 18:43:24 2018

@author: Administrator
"""
#%%
#此脚本档用在linux系统上时，需要确认是否使用‘GBK’解码，可能是用‘utf-8’
import subprocess
import os
import shutil
import traceback
import glob
import sys


def get_code_standard():
    
    #作用：查看当前程序运行操作系统的类型，并以此生成shell或cmd的stdout stderr解码方式（GBK,UTF-8）。
    #参数说明：
    #返回值说明：返回解码方式
    #举例：
    #调用：sys
    #被调用：err_log_write()
    
    if 'win' in sys.platform:
        code_standard = 'GBK'
    if 'linux' in sys.platform:
    #linux上的编码方式是‘utf-8’吗，有待确认
        code_standard = 'utf-8'
    return code_standard

#-------------------------------以下内容为错误信息log记录

def log_write(log_path,paragr):    
        
    #作用原理：将信息写入txt文件
    #参数说明：paragr是一个双重嵌套list即：paragr[i] 代表要输出的第i行，而paragr[i][j]代表要输出的第i行的第j个元素。
    #返回值说明：无返回值
    #举例：line1 = ['','>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>']；line2 = ['Number Of dir_file： ',i]
    #举例：用上面的line1 line2来构造paragr = [line1,line2]
    #调用：traceback
    #被调用：
    #bug：
    f = open(log_path ,'a')
    for i in range(0,len(paragr)):
        for j in range(0,len(paragr[i])):
            f.write(paragr[i][j])
        f.write('\n')
    traceback.print_exc(file=f)
    f.flush()
    f.close()

def dircp_log_write(dircp_log_path,item,i,j):
        
    #作用：用于记录程序中运行中的异常，以备查看
    #参数说明：dircp_log_path是指存放异常日志的完整路径（包括日志文件名）
    #参数说明：i报错的在总的循环中是第几个
    #参数说明：j报错次数的记录，用于查看日志共出现多少个错误
    #返回值说明：
    #举例：err_log(r'H:\data\HuXianZhang\01\before\unpack_and_copy_err_log.txt',5),表示记录第五个错误
    #调用：traceback模块
    #被调用：unpack_and_copy()  
    line1 = ['','>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>']
    line2 = ['Number Of dir_file： ',str(i)]
    line3 = ['Number Of Dircp: ',str(j)]
    line4 = ['Do NOt Need Extract: ',item]
    line5 = ['\n' * 2]
    paragr = [line1,line2,line3,line4,line5]
    log_write(dircp_log_path,paragr)
    
def err_log_write(err_log_path,item,stdout_err,i,j):
    
    #作用：用于记录程序中运行中的异常，以备查看
    #参数说明：err_log_path是指存放异常日志的完整路径（包括日志文件名）
    #参数说明：i报错的在总的循环中是第几个
    #参数说明：j报错次数的记录，用于查看日志共出现多少个错误
    #返回值说明：
    #举例：err_log_write(r'H:\data\HuXianZhang\01\before\unpack_and_copy_err_log.txt',5),表示记录第五个错误
    #调用：traceback模块  get_code_standard()
    #被调用：unpack_and_copy()  
    code_standard = get_code_standard()

    line1 = ['','@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@']
    line2 = ['Number Of dir_file： ',str(i)]
    line3 = ['Number of Error: ',str(j)]
    line4 = ['Extract Failure: ',item]
    line5 = ['stdout: \n', stdout_err[0].decode(code_standard)]
    line6 = ['stderr: \n', stdout_err[1].decode(code_standard)]
    line7 = ['\n' * 2]
    paragr = [line1,line2,line3,line4,line5,line6,line7]
    log_write(err_log_path,paragr)

    
#---------------------------------以下内容为错误信息print输出

def info_print(paragr):
    
    #作用原理：输出程序的运行信息
    #参数说明：paragr是一个双重嵌套list即：paragr[i] 代表要输出的第i行，而paragr[i][j]代表要输出的第i行的第j个元素。
    #返回值说明：无返回值
    #举例：line1 = ['','>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>']；line2 = ['Number Of dir_file： ',i]
    #举例：用上面的line1 line2来构造paragr = [line1,line2]
    #调用：
    #被调用：dircp_info_print()   err_info_print()   succ_info_print()
    #bug：
    for i in range(0,len(paragr)):
        for j in range(0,len(paragr[i])):
            print(paragr[i][j],end='')
        print('')


def dircp_info_print(i,k,item):
    
    #作用原理：输出复制文件夹的信息
    #参数说明：
    #参数说明：
    #返回值说明：无返回值
    #举例：
    #调用：inro_print()
    #被调用：unpack_and_copy()
    #bug：
    line1 = ['','>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>']
    line2 = ['Number Of dir_file： ',i]
    line3 = ['Number of Dircp: ',k]
    line4 = ['Do NOt Need Extract: ',item]
    line5 = ['\n' * 2]
    paragr = [line1,line2,line3,line4,line5]
    info_print(paragr)
    

def err_info_print(i,j,item,stdout_err):
    #作用原理：输出成解压报错信息
    #参数说明：
    #参数说明：
    #返回值说明：无返回值
    #举例：
    #调用：inro_print()
    #被调用：unpack_and_copy()
    #bug：
    code_standard = get_code_standard()
    line1 = ['','@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@']
    line2 = ['Number Of dir_file： ',i]
    line3 = ['Number of Error: ',j]
    line4 = ['Extract Failure: ',item]
    line5 = ['stdout: \n', stdout_err[0].decode(code_standard)]
    line6 = ['stderr: \n', stdout_err[1].decode(code_standard)]
    line7 = ['\n' * 2]
    paragr = [line1,line2,line3,line4,line5,line6,line7]
    info_print(paragr)


def succ_info_print(i,l,item):
    #作用原理：输出成功解压的信息
    #参数说明：
    #参数说明：
    #返回值说明：无返回值
    #举例：
    #调用：inro_print()
    #被调用：unpack_and_copy()
    #bug：
    line1 = ['','!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!']
    line2 = ['Number Of dir_file： ',i]
    line3 = ['Number of Archive: ',l]
    line4 = ['Extract Success: ',item]
    line5 = ['\n' * 2]
    paragr = [line1,line2,line3,line4,line5]
    info_print(paragr)


def search_dirs(start_path,level_need):
    
    #作用：生成满足条件的所有文件路径。
    #参数说明：start_path 跟调用次函数的同名变量意义一致
    #参数说明：level_need 跟调用次函数的同名变量意义一致
    #返回值说明：
    #举例：
    #调用：glob模块
    #被调用：unpack_and_copy
    level = (os.sep + '*') * level_need
    search_res = glob.glob(start_path + level)
    return search_res

def pading(inp_str):
    
    #作用：在文件夹名、文件名前添加路径分隔符（\for win;/for linux）及双引号（当文件夹名、文件名中包含空格，需要添加双引号，使shell或cmd正确识别）
    #参数说明：inp_str文件夹名或者文件名如‘after’（名字中不含有空格），‘zhang san’（名字中含有空格）
    #返回值说明：\"after"   \"zhang san"   \"张 三”
    #举例：pading('after')   
    #调用：os模块
    #被调用：space_in_path()
    inp_str = os.sep + r'"' + inp_str + r'"'
    #os.sep是为了使本函数具有linux windows平台的通用性   
    return inp_str

def space_in_path(inp_str):
    
    #作用：以linux的shell命令为例，因为文件夹aa bb/中有空格，所以执行命令 cd aa bb/ 时相当于执行了cd aa，所以会结果会出错
    #作用：但很多数据整理者在命名文件夹时文件名中时常包含空格，而解决这个问题的办法就是使用命令cd “aa bb/”来代替。所以本函数的
    #作用：目的就是将linux路径/home/user 001/mri dp转换成'/"home"/"user 001"/"mri dp"'
    #作用：将windows路径D:\Program Files/mri dp转换成D：'\\"Program Files"\\"mri dp"'这种形式
    #参数说明：
    #返回值说明：
    #举例：
    #调用:os模块  pading()
    #被调用：sevenzip_unpack()
    drive_dir = os.path.split(inp_str)
    pure_dir = ''
    if  drive_dir[0] != inp_str:
        pure_dir = space_in_path(drive_dir[0])
    if drive_dir[1] !='':
        pure_dir = pure_dir + pading(drive_dir[1])
    if drive_dir[1] =='':
        pure_dir = os.path.splitdrive(drive_dir[0])[0]
    return pure_dir

def sevenzip_unpack(file_path,outp_path,sevenzip_path):
    
    #作用：使用subprocess.Popen()调用shell（linux）、cmd（win7）中的7z命令（需已经安装7z软件，且将7z.exe放在环境变量中），来实现对file_path解压缩的功能。
    #作用：在windows平台上7z的cmd命令是：7z x H:\data\huxianzhang最原始文件\01\after\"HY_072_2_CS (G).zip"  -oH:\data\HuXianZhang
    #作用：因为HY_072_2_CS (G).zip这个文件名子中间含有空格，会导致报错，所以需要加双引号（space_in_path()函数就是此目的）
    #作用：其余的如data、huixianzhang最原始文件、after等文件夹名字，不含有空格，所以可以加双引号、也可以不加双引号
    #参数说明：file_path是需要解压缩的文件的完整路径（包括文件名）
    #参数说明：outp_path是加压缩后文件的存放路径
    #参数说明：sevenzip_path，是7z命令的完整路径，是预留变量，暂时还没有用到。目的是在linux中，即使7z不在环境变量中，也可以在本程序中临时设置环境变量，
    #参数说明：--通过完整路径来调用7z。
    #返回值说明：
    #举例：sevenzip_unpack(r'H:\data\huxianzhang最原始文件',r'H:\data\HuXianZhang',r'')
    #调用：subprocess模块  space_in_path()
    #被调用：unpack_and_copy()
    cmd = '7z  x ' + space_in_path(file_path) + r' -o' + space_in_path(outp_path)
    #print(cmd)
    popen_subj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    stdout_err = popen_subj.communicate()
    return stdout_err


def dir_copy(inp_path,outp_path):
        
    #作用：将inp_path文件夹中的所有内容都复制到outp_path中
    #参数说明：inp_path是一个文件夹的路径
    #参数说明：outp_path是目的路径
    #返回值说明：无
    #举例：dir_copy(r'H:\data\YangHong最原始文件\2017',r'H:\data\YangHong')
    #调用：shutil模块
    #被调用：unpack_and_copy()
    shutil.copytree(inp_path,outp_path)

    
def file_copy(inp_path,outp_path):
    
    #作用：将inp_path文件复制到outp_path中
    #参数说明：inp_path是一个完整的文件路径（包括文件名）
    #参数说明：outp_path是目的路径
    #返回值说明：无
    #举例：file_copy(r'H:\data\huxianzhang最原始文件\01\afterHY_072_2_CS (G).zip',r'H:\data\HuXianZhang\01\after')
    #调用：shutil模块
    #被调用：unpack_and_copy()
    if not os.path.exists(outp_path):
        os.makedirs(outp_path)
    shutil.copy(inp_path,outp_path)
	
	
	
#%%
#python对.7z文件的解压缩支持有问题，pylzma/py7zlib总是有bug，故暂时不采用这种实现方式。
#import shutil
#import rarfile
#import py7zlib
#def unpack_rar(file_path,outp_path):
    #"""unrar zip file"""
    #rar = rarfile.RarFile(file_path)
    #rar.extractall(outp_path)
    #rar.close()
#shutil.register_unpack_format('rar',[r'.rar'],unpack_rar)
#
#def unpack_7z(file_path,outp_path):
    #"""unrar 7z file"""
    ##实现参考pylzma_py7zlib.ipynb文件。
    ##上面这个命令只能被执行一次，否则就会出现错误。
    ##一开始将此行代码放在self_def_unpack()这个函数里面，则每次调用
    ##self_def_unpack()这个函数都要把此行代码执行一遍，所以会反复报错。
#def python_unpack(file_path,outp_path):
    #shutil.unpack_archive(file_path,outp_path)
	
#%%
#有些原始数据是将一个subject的所有modality都压缩到一个文件夹里，MRIConvert不能处理这些压缩文件，所以本软件的目的就是为了将
#这些压缩文件解压到同名文件夹下，并且复制到与原来文件路径一致的文件夹中（即，保留原来的文件路径结构）
def unpack_and_copy(start_path,level_need,sevenzip_path=''):
    #作用：批量解压缩MRI数据，为使用MRIConvert做准备
    #参数说明：start_path是指所有压缩文件的共有目录，level_need是指压缩文件在共有目录之间的层级数
    #参数说明：sevenzip_path表示7z的路径，但win7批处理脚本很难写，所以在win7上应直接把7z的路径设为环境变量
    #返回值说明：
    #举例：如果需要解压缩的文件为H:\data\huxianzhang最原始文件\01\after\HY_072_2_CS (G).zip则：
    #举例：unpack_and_copy(r'H:\data\huxianzhang最原始文件',3,r'H:\data\HuXianZhang',)
    #调用：
    #被调用：sevenzip_unpack()   err_log()
    
    #参数准备
    i = 0; k = 0; j = 0; l = 0
    #outp_path必须是H:\data\huxianzhang最原始文件_ready4d2n(ready for dcm to nii)这个文件夹，所以无需作为函数参数输入。
    #其他的MRIConvert、dcm2nii等dicom至nii转换软件都是在\data\huxianzhang最原始文件_ready4d2n基础上进行的。
    last_name = os.path.split(start_path)[1]
    outp_path = start_path.replace(last_name,last_name + '_ready4d2n')
    search_res = search_dirs(start_path,level_need)
    for item in search_res:
        i = i + 1
        medi_path = item.replace(start_path,outp_path)
        medi_path_shallow = os.path.split(medi_path)[0]
        medi_path_deep = os.path.splitext(medi_path)[0]
        dircp_log_path = medi_path_shallow + os.sep + sys._getframe().f_code.co_name + '_dircp_log.txt'
        err_log_path = dircp_log_path.replace('_dircp_log.txt','_err_log.txt')
        if os.path.isdir(item):
            k = k + 1
            dir_copy(item,medi_path)
            dircp_log_write(dircp_log_path,item,i,k)
            dircp_info_print(i,k,item)
        else:
            stdout_err = sevenzip_unpack(item,medi_path_deep,sevenzip_path)
            #stdout_err是一个list，stdout_err[0]含有stdout输出，stdout_err[1]含有stderr输出。
            if stdout_err[1] != b'':
                j = j + 1
                #解压缩失败的情况下（1.压缩文件自身问题解压缩失败;2.非压缩文件导致解压缩失败），将未成功的文件拷贝
                #到medi_path_shallow里，然后继续加压缩下一个文件。
                file_copy(item,medi_path_shallow)
                #将发生错误的信息写到中log_path中
                err_log_write(err_log_path,item,stdout_err,i,j)
                #输出解压缩失败的提示信息
                err_info_print(i,j,item,stdout_err)
            else:
                l = l + 1
                succ_info_print(i,l,item)
    return

	
#%%
#测试代码:unpack_and_copy(r'H:\test_uncompressing',2)
#unpack_and_copy(r'H:\data\huxianzhang最原始文件\03\before',1,r'H:\data\HuXianZhang')
#unpack_and_copy(r'H:\data\YangHong',1)